Terraformに便利機能を提供するラッパーツールTerragruntの紹介
はじめに
こんにちは、中山です。
先日GitHubのトレンドを見ていたところ、面白そうなツールを発見しました。Terragruntというものです。これはTerraformのラッパーツールで、いろいろと便利な機能を提供してくれます。今回このツールを利用してみたのでエントリにまとめます。
Terragruntは何を解決するために作られたのか
作者の方のブログ(Add Automatic Remote State Locking and Configuration to Terraform with Terragrunt)や、GitHubのREADMEに詳しく書かれていますが、以下の2つの問題を解決するためのツールです。
- リモートステートの有効化忘れ
- ロック機能
以下それぞれについて説明します。
リモートステートの有効化忘れ
Terraformは拡張子に .tfstate
が付くファイル(ステートファイル)にリモートのリソース情報をJSON形式で書き込みます。これは、リソースの実際の状態が記述されたファイルです。そして、拡張子に .tf
が付くファイルにリソースのあるべき状態を記述します。Terraformはこの2つのファイル間で差分を確認し、変更があったらそれを適用するというモデルで実行されます。そのため、ステートファイルの管理は重要です。コードの管理を一人でしている場合は特に意識しないですが、複数人のチームで管理している場合は、チーム内で常に同じステートファイルを共有する必要があります。
幸い、ステートファイルを共有する機能が標準で提供されています。過去のエントリでも何度か触れていますがリモートステートという機能で、ステートファイルをリモートのストレージ(S3/Atlas/consulなど)に保存することが可能です。リモートステートを利用することで、Terraformを実行する際にステートファイルをリモートから取得し、ローカルで更新したらそれをリモートにも反映することが可能になります。こうすることで、チーム内でステートファイルを共有できるという訳です。
リモートステートの有効化はコマンドラインから実施します。以下はS3をバックエンドにした場合のコマンドです。
$ terraform remote config \ -backend=s3 \ -backend-config='bucket=<bucket-name>' \ -backend-config='key=terraform.tfstate' \ -backend-config='region=ap-northeast-1'
リモートステートはとても便利な機能なのですが、問題点があります。Terraform実行時にリモートステートの有効化を忘れるという問題です。忘れたままステートファイルを更新し、別の開発者がTerraformを実行しようとした際に、不具合を起こすという問題があります。要はヒューマンエラーです。
Terragruntではこの問題を防止するために、Terraformの実行前に自動でリモートステートを有効化する仕組みを提供してくれます。
ロック機能
Terraformにはもう一つ問題点があります。それはロック機能が標準で無いという点です。つまり、多重処理を実施されてしまう可能性があります。別々の開発者が同時にTerraformを実行した場合、お互いの変更内容がコンフリクトしてしまう可能性があります。
この問題を解決するため、TerragruntはDynamoDBを利用してロック機能を提供します。Terraformの実行前にDynamoDBのテーブルを更新して、テーブル内に特定のアイテムが存在している間はある開発者がTerraform実行中とみなし、他の開発者が実行できないようにしてくれます。
Terragruntが解決しようとしている問題及び解決方法をご紹介しました。では早速使ってみましょう。
コード
サンプルとなるコードをGitHubに置きました。ご自由にお使いください。
インストール
TerragruntはTerraformと同じようにGo言語製のツールなので、インストールは簡単です。現時点(2016/08/12)ではHomebrewなどでインストール出来ないようなので、GitHub Releaseから自分の環境にあった最新バイナリをダウンロードして下さい。ダウンロード完了後、パスを通して以下のように表示されればインストール完了です。
$ terragrunt --help DESCRIPTION: terragrunt - Terragrunt is a thin wrapper for [Terraform](https://www.terraform.io/) that supports locking via Amazon's DynamoDB and enforces best practices. Terragrunt forwards almost all commands, arguments, and options directly to Terraform, using whatever version of Terraform you already have installed. However, before running Terraform, Terragrunt will ensure your remote state is configured according to the settings in the .terragrunt file. Moreover, for the apply and destroy commands, Terragrunt will first try to acquire a lock using DynamoDB. For documentation, see https://github.com/gruntwork-io/terragrunt/. USAGE: terragrunt <COMMAND> COMMANDS: apply Acquire a lock and run 'terraform apply' destroy Acquire a lock and run 'terraform destroy' release-lock Release a lock that is left over from some previous command * Terragrunt forwards all other commands directly to Terraform GLOBAL OPTIONS: --help, -h show help --version, -v print the version VERSION: v0.0.10 AUTHOR(S): Gruntwork <www.gruntwork.io>
.terragrunt
ファイルの内容
Terragruntはカレントディレクトリに設置された .terragrunt
というファイルから設定内容を読み取ります。書式はHCLで記述します。つまりTerraformのtfファイルと同じ書式で記述可能です。以下に今回使った設定ファイルを記載します。
# Configure Terragrunt to use DynamoDB for locking dynamoDbLock = { stateFileId = "my-app" awsRegion = "ap-northeast-1" tableName = "terragrunt_locks" maxLockRetries = 360 } # Configure Terragrunt to automatically store tfstate files in S3 remoteState = { backend = "s3" backendConfigs = { encrypt = "true" bucket = "terragrunt-demo" key = "terraform.tfstate" region = "ap-northeast-1" } }
見たままではありますが、それぞれの意味は以下のとおりです。
項目 | 内容 |
---|---|
dynamoDbLock |
DynamoDBの設定 |
StateFileId |
属性に書き込む内容 |
awsRegion |
DynamoDBのリージョン |
tableName |
テーブル名 |
maxLockRetries |
ロック取得まで何回リトライ処理をするか |
remoteState |
リモートステートの設定 |
backend |
バックエンドの指定 |
backendConfigs |
バックエンドの設定 |
encrypt |
S3の暗号化を有効化するか(現時点ではまだ実装されてなさそう) |
bucket |
ステートファイルを保存するバケット名 |
key |
ステートファイルを保存するキー名 |
region |
S3のリージョン |
コマンドラインの使い方
Usageを見ると分かりますが、Terragruntは apply
と destroy
サブコマンド実行時にDynamoDBでロック処理を実施し、 release-lock
サブコマンドで明示的にロックを解除、その他のコマンドはTerraformにそのまま渡されるという仕組みで動作します。
こちらのコードを見ると、case文に記載されているコマンド入力時に自動でリモートステートが有効化してくれるようです。今回リモートステートのバックエンドにはS3を利用します。まず、ステートファイル保存用のバケットを作成してください。バケット名は .terragrunt
ファイルの内容と合わせる必要があります。
$ aws s3 mb s3://terragrunt-demo make_bucket: s3://terragrunt-demo/
バケットの作成が完了したら、 plan
サブコマンドを実行してみましょう。
$ terragrunt plan [terragrunt] 2016/08/10 10:55:22 Configuring remote state for the s3 backend [terragrunt] 2016/08/10 10:55:22 Running command: terraform remote config -backend s3 -backend-config=encrypt=true -backend-config=bucket=terragrunt-demo -backend-config=key=terraform.tfstate -backend-config=region=ap-northeast-1 Remote state management enabled Remote state configured and pulled. [terragrunt] 2016/08/10 10:55:24 Running command: terraform plan <snip>
表示内容を確認すると、 plan
サブコマンド実行前にリモートステートを有効化しています。カレントディレクトリの .terraform
ディレクトリを見てみると、ステートファイルが移動していることが確認できます(リモートステートを有効化するとステートファイルがこの場所に移動します)。
$ ls -l .terraform total 8 -rw-r--r-- 1 knakayama staff 458 Aug 10 10:55 terraform.tfstate
続いて apply
サブコマンドでリソースを作成してみます。
$ terragrunt apply [terragrunt] 2016/08/10 11:17:41 Remote state is already configured for backend s3 [terragrunt] 2016/08/10 11:17:41 Attempting to acquire lock for state file my-app in DynamoDB [terragrunt] 2016/08/10 11:17:42 Lock table terragrunt_locks does not exist in DynamoDB. Will need to create it just this first time. [terragrunt] 2016/08/10 11:17:42 Creating table terragrunt_locks in DynamoDB [terragrunt] 2016/08/10 11:17:43 Table terragrunt_locks is not yet in active state. Will check again after 10s. [terragrunt] 2016/08/10 11:17:53 Success! Table terragrunt_locks is now in active state. [terragrunt] 2016/08/10 11:17:53 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks [terragrunt] 2016/08/10 11:17:55 Lock acquired! [terragrunt] 2016/08/10 11:17:55 Running command: terraform apply <snip> [terragrunt] 2016/08/10 11:20:58 Attempting to release lock for state file my-app in DynamoDB [terragrunt] 2016/08/10 11:20:58 Lock released!
表示内容からDynamoDBのテーブル作成、ロックの有効化を実施していることが確認できます。リソースの作成が完了するとロックをリリースした旨表示されています。
DynamoDBのテーブルはどうなっているのでしょうか。確認してみます。
$ aws dynamodb describe-table \ --table-name terragrunt_locks { "Table": { "TableArn": "arn:aws:dynamodb:ap-northeast-1:************:table/terragrunt_locks", "AttributeDefinitions": [ { "AttributeName": "StateFileId", "AttributeType": "S" } ], "ProvisionedThroughput": { "NumberOfDecreasesToday": 0, "WriteCapacityUnits": 1, "ReadCapacityUnits": 1 }, "TableSizeBytes": 0, "TableName": "terragrunt_locks", "TableStatus": "ACTIVE", "KeySchema": [ { "KeyType": "HASH", "AttributeName": "StateFileId" } ], "ItemCount": 0, "CreationDateTime": 1470795463.149 } }
.terragrunt
ファイルで定義した名前でテーブルが作成され、スキーマとして StateFileId
が存在していることが確認できます。アイテムの内容を確認してみましょう。
$ aws dynamodb scan \ --table-name terragrunt_locks { "Count": 0, "Items": [], "ScannedCount": 0, "ConsumedCapacity": null }
ロックをリリースしたのでこの時点では空っぽのようです。では、ロック中の内容を確認してみます。 taint
サブコマンドで aws_spot_request
リソースを「汚染」させ、リソースを意図的に再作成させます。
$ terragrunt taint aws_spot_instance_request.web [terragrunt] 2016/08/10 11:31:48 Remote state is already configured for backend s3 [terragrunt] 2016/08/10 11:31:48 Running command: terraform taint aws_spot_instance_request.web The resource aws_spot_instance_request.web in the module root has been marked as tainted!
続いて apply
サブコマンドでリソースを作成します。作成中(ロック中)にDynamoDBの内容を表示した結果が以下です。
$ aws dynamodb scan \ --table-name terragrunt_locks { "Count": 1, "Items": [ { "Username": { "S": "cm-nakayama.koji" }, "Ip": { "S": "192.168.43.4" }, "CreationDate": { "S": "2016-08-10 02:33:32.834892784 +0000 UTC" }, "StateFileId": { "S": "my-app" } } ], "ScannedCount": 1, "ConsumedCapacity": null }
アイテムの中に4つの属性が作成されています。
属性 | 内容 |
---|---|
Username |
IAMユーザ名 |
Ip |
Terragrunt実行ホストのIPアドレス |
CreationDate |
Terragrunt実行時間 |
StateFileId |
.terragrunt ファイルで定義した内容 |
これらのアイテムが存在している間はロック中と判断し、Terraformの同時実行を防止してくれます。もし、ロック中に別の開発者が apply
サブコマンドを実行した場合、 .terragrunt
で定義した maxLockRetries
実施後、以下のようにエラーを表示してくれます。
$ terragrunt apply [terragrunt] 2016/08/10 11:41:27 Remote state is already configured for backend s3 [terragrunt] 2016/08/10 11:41:27 Attempting to acquire lock for state file my-app in DynamoDB [terragrunt] 2016/08/10 11:41:28 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks [terragrunt] 2016/08/10 11:41:30 Someone already has a lock on state file my-app! [email protected] acquired the lock on 2016-08-10 02:41:25.932320746 +0000 UTC. [terragrunt] 2016/08/10 11:41:30 Will try to acquire lock again in 10s. [terragrunt] 2016/08/10 11:41:40 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks [terragrunt] 2016/08/10 11:41:41 Someone already has a lock on state file my-app! [email protected] acquired the lock on 2016-08-10 02:41:25.932320746 +0000 UTC. [terragrunt] 2016/08/10 11:41:41 Will try to acquire lock again in 10s. [terragrunt] 2016/08/10 11:41:51 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks [terragrunt] 2016/08/10 11:41:52 Someone already has a lock on state file my-app! [email protected] acquired the lock on 2016-08-10 02:41:25.932320746 +0000 UTC. [terragrunt] 2016/08/10 11:41:52 Will try to acquire lock again in 10s. [terragrunt] 2016/08/10 11:42:02 Unable to acquire lock for item my-app after 3 retries.
最後に destroy
サブコマンドでリソースを削除してみましょう。
$ terragrunt destroy [terragrunt] 2016/08/10 12:24:21 Remote state is already configured for backend s3 [terragrunt] 2016/08/10 12:24:21 Attempting to acquire lock for state file my-app in DynamoDB [terragrunt] 2016/08/10 12:24:22 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks [terragrunt] 2016/08/10 12:24:24 Lock acquired! [terragrunt] 2016/08/10 12:24:24 Running command: terraform destroy Do you really want to destroy? Terraform will delete all your managed infrastructure. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes <snip> [terragrunt] 2016/08/10 12:25:33 Attempting to release lock for state file my-app in DynamoDB [terragrunt] 2016/08/10 12:25:34 Lock released!
ロックの取得/開放以外はTerraformを直接実行した場合と同じです。
まとめ
いかがでしょうか。
私としてはCircleCIなどのCIサービスと連携させて、Terraformの実行をそこに集約させればリモートステートの有効化忘れ/ロック機能について考慮しなくてもいいのではと考えています。常に一箇所でTerraformを実行させれば、これらの問題を解決できるからです。ただし、CIサービスなどを利用せずに、Terraform単体でこういった機能を実装してくれると、なかなか便利だと思います。
作者の方のブログを見ると、このツールはTerraformに対するPR的な位置付けのようです。つまり、Terraform単体ではこれこれの機能が不足している、だからそれを補うツールを作った。しかし、Terraformのコア機能として取り込もうとすると時間かかるし、現在の実装方法はAWSに寄りすぎてコミュニティに受け入れられるか微妙。コミュニティからのフィードバックを受けて将来的にはTerraformのコア機能として実装したい。
個人的にはとてもおもしろい試みだと思うので、今後も追いかけていこうと思います。
本エントリがみなさんの参考になれば幸いです。